//
// Manta - Structural Variant and Indel Caller
// Copyright (c) 2013-2019 Illumina, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//

/// \file
/// \author Chris Saunders
///

#pragma once

#include "SingleRefAlignerShared.hpp"

/// \brief align a contig to reference and allow very large insert or deletion events
///
/// this is essentially a regular global alignment with affine gap penalties for small
/// indels and a non-affine gap state for large indels, the open cost for large indels
/// is (typically) much higher but the extension cost is zero.
///
/// transition from insert to delete is free and allowed
/// transition from jumpDel to insert is free and allowed
/// transition from jumpIns to delete is free and allowed
///
template <typename ScoreType>
struct GlobalLargeIndelAligner : public SingleRefAlignerBase<ScoreType> {
  /// \param largeIndelScore is the 'gap open' for the large indels
  ///
  GlobalLargeIndelAligner(const AlignmentScores<ScoreType>& scores, const ScoreType largeIndelScore)
    : SingleRefAlignerBase<ScoreType>(scores), _largeIndelScore(largeIndelScore)
  {
  }

  /// returns alignment path of query to reference
  template <typename SymIter>
  void align(
      const SymIter               queryBegin,
      const SymIter               queryEnd,
      const SymIter               refBegin,
      const SymIter               refEnd,
      AlignmentResult<ScoreType>& result) const;

private:
  // insert and delete are for seq1 wrt seq2
  struct ScoreVal {
    ScoreType getScore(const AlignState::index_t i) const
    {
      switch (i) {
      case AlignState::MATCH:
        return match;
      case AlignState::INSERT:
        return ins;
      case AlignState::DELETE:
        return del;
      case AlignState::JUMP:
        return jumpDel;
      case AlignState::JUMPINS:
        return jumpIns;
      default:
        assert(false && "Unexpected Index Value");
        return 0;
      }
    }

    ScoreType match;
    ScoreType ins;
    ScoreType del;
    ScoreType jumpIns;
    ScoreType jumpDel;
  };

  struct PtrVal {
    typedef uint16_t code_t;

    /// for state i, return the highest scoring previous state
    /// to use during the backtrace?
    AlignState::index_t getStatePtr(const AlignState::index_t i) const
    {
      return static_cast<AlignState::index_t>(getStateCode(i));
    }

  private:
    code_t getStateCode(const AlignState::index_t i) const
    {
      switch (i) {
      case AlignState::MATCH:
        return match;
      case AlignState::INSERT:
        return ins;
      case AlignState::DELETE:
        return del;
      case AlignState::JUMP:
        return jumpDel;
      case AlignState::JUMPINS:
        return jumpIns;
      default:
        assert(false && "Unexpected Index Value");
        return 0;
      }
    }

  public:
    /// pack 3x5 bits into 2 bytes:
    code_t match : 3;
    code_t ins : 3;
    code_t del : 3;
    code_t jumpDel : 3;
    code_t jumpIns : 3;
  };

  static uint8_t max5(
      ScoreType&      max,
      const ScoreType v0,
      const ScoreType v1,
      const ScoreType v2,
      const ScoreType v3,
      const ScoreType v4)
  {
    max         = v0;
    uint8_t ptr = 0;
    if (v1 > max) {
      max = v1;
      ptr = 1;
    }
    if (v2 > max) {
      max = v2;
      ptr = 2;
    }
    if (v3 > max) {
      max = v3;
      ptr = 3;
    }
    if (v4 > max) {
      max = v4;
      ptr = 4;
    }
    return ptr;
  }

  // add the matrices here to reduce allocations over many alignment calls:
  typedef std::vector<ScoreVal> ScoreVec;
  mutable ScoreVec              _score1;
  mutable ScoreVec              _score2;

  typedef basic_matrix<PtrVal> PtrMat;
  mutable PtrMat               _ptrMat;

  const ScoreType _largeIndelScore;
};

#include "alignment/GlobalLargeIndelAlignerImpl.hpp"
